﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace msilgc.runtime {
    internal class AotMarshallingCodeGenerator {
        public IEnumerable<string> WriteCode(IEnumerable<MethodInfo> methods) {
            int index = (-1);
            foreach (var method in methods) {
                yield return WriteClass(method, ++index);
            }
        }

        private string WriteClass(MethodInfo method, int index) {
            var code = new StringBuilder();
            code.AppendLine("using System;");
            code.AppendLine("using System.IO;");
            code.AppendLine("using System.Reflection;");
            code.AppendLine("using msilgc.common;");
            code.AppendLine("using msilgc.common.Types;");
            code.AppendLine("using msilgc.driver.cuda;");

            code.AppendFormat("namespace {0} {{ {1}", method.DeclaringType.Namespace, Environment.NewLine);
            code.AppendFormat("public partial class {0} {{ {1}", method.DeclaringType.Name, Environment.NewLine);

            DelegateWriter.WriteDelegateDeclaration(code, method, method.Name, false);

            var marshalledReturnType = DelegateWriter.GetMarshalledTypeString(method.ReturnType);

            code.AppendFormat("public {0} {1}(", marshalledReturnType, method.Name);

            int i = (-1);
            foreach (var parameter in method.GetParameters()) {
                if (!DelegateWriter.IsGrainParameter(parameter)) {
                    i++;
                    code.Append(DelegateWriter.GetUnMarshalledTypeString(parameter.ParameterType));
                    code.Append(" arg_");
                    code.Append(i.ToString());
                    code.Append(", ");
                }
            }
            code.AppendLine("Grain threadGrain, Grain dataGrain, int devId) {");

            //method impl

            code.AppendFormat(
                "{0}Delegate methodHandle = ({0}Delegate)msilgc.common.NativeMethodHelperFactory.Create()" +
                ".GetMethodHandle(\"driver.dll\", \"{0}\", typeof({0}Delegate));",
                method.Name);
            code.AppendLine();

            i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (!(DelegateWriter.IsGrainParameter(parameter) || DelegateWriter._IsPrimitive(parameter.ParameterType))) {
                    code.AppendLine("var parg_" + i + " = PinnedMemoryResource.Create(arg_" + i + ");");
                }
            }

            code.Append("try { methodHandle(");
            i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (DelegateWriter.IsGrainParameter(parameter)) {
                } else if (DelegateWriter._IsPrimitive(parameter.ParameterType)) {
                    code.Append("arg_" + i + ",");
                } else {
                    code.Append("parg_" + i + ".Data,");
                }
            }
            code.AppendLine("threadGrain, dataGrain, devId); } finally { ");

            i = (-1);
            foreach (ParameterInfo parameter in method.GetParameters()) {
                i++;
                if (!(DelegateWriter.IsGrainParameter(parameter) || DelegateWriter._IsPrimitive(parameter.ParameterType))) {
                    code.AppendLine("parg_" + i + ".Dispose();");
                }
            }
            code.AppendLine("}");
            code.AppendLine("NativeMethodHelperFactory.Create().ThrowForWin32Error();");

            //end method impl

            code.AppendLine("}");
            code.AppendLine("}");
            code.AppendLine("}");
            return code.ToString();
        }
    }
}